package org.dei.perla.sys.device.fpc.MPPT;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import org.dei.perla.parser.clauses.OnUnsupportedSampleRateOptions;
import org.dei.perla.parser.expressions.Constant;
import org.dei.perla.parser.expressions.OperationNotSupportedException;
import org.dei.perla.parser.util.PairValue;
import org.dei.perla.sys.dbutils.PerlaCalibrationLoader;
import org.dei.perla.sys.dbutils.PerlaCalibrationLoaderException;
import org.dei.perla.sys.device.fpc.DBRecordUtil;
import org.dei.perla.sys.device.fpc.FPC;
import org.dei.perla.sys.device.fpc.FPCInitializationPhaseException;
import org.dei.perla.sys.device.fpc.PerlaDataTransformException;
import org.dei.perla.sys.device.fpc.MPPT.datastructures.MpptData;
import org.dei.perla.sys.device.fpc.dspic.datastructures.DspicDataBufferOnEvent;
import org.dei.perla.sys.llqruntime.datastructures.QueryRecordStructure;
import org.dei.perla.sys.llqruntime.datastructures.Record;
import org.dei.perla.utils.dataconverter.ArrayUtility;
import org.dei.perla.utils.dataconverter.DataConverter;
import org.dei.perla.utils.dataconverter.annotations.StructInfo;
import org.dei.perla.utils.logger.SimpleLogger;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.pipe.PipePair;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

public class MPPTFpc extends FPC{

	


	private final Pipe<AdapterFpcMessage> pFpcPipeIn;
	private final Pipe<ArrayList<PairValue<String, ? extends Constant>>> pDataCollectorPipeIn;
	private final Thread pFpcListener;
	private final Waiter<Pipe<AdapterFpcMessage>> pFpcPipeInWaiter;
	private QueryRecordStructure mpptDataRecordStructure;
	private final int pDevNumber;
	private final int structSize;
	private ArrayList<Pipe<Record>> pDataCollectorPipeOut;
	
	
	public MPPTFpc(String deviceName, PipePair<AdapterFpcMessage, AdapterFpcMessage> parPipePair, int parDevNumber) {
		StructInfo structInfo;
		pDeviceName = new String(deviceName);
		pDataCollectorPipeIn = new Pipe<ArrayList<PairValue<String, ? extends Constant>>>(
				"MPPT " + parDevNumber + " DataCollectorPipeIn");
		pDataCollectorPipeIn.start();
		pDataCollectorPipeOut = new ArrayList<Pipe<Record>>();
		pFpcPipeIn = parPipePair.getInPipe();
		pFpcPipeInWaiter = new Waiter<Pipe<AdapterFpcMessage>>();
		pFpcPipeInWaiter.addWaitable(pFpcPipeIn.getWaitable());
		pFpcPipeInWaiter.start();
		pDevNumber = parDevNumber;
		
		structInfo = MpptData.class.getAnnotation(StructInfo.class);
		structSize = structInfo.totalStructSize();
		
		// Creo la struttura del record per il campionamento continuo
		mpptDataRecordStructure  = DBRecordUtil.createMPPTDataRecordStructure();
		
		pFpcListener = new Thread(new PipeReader());
		pFpcListener.start();
		try {
			initCoefficients();
		} catch (FPCInitializationPhaseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (PerlaCalibrationLoaderException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Spacchetta il blob inviato dai livelli inferiori
	 */
	@Override
	protected void mngNormalMsg(byte[] parPayload) {
			manageData(ArrayUtility.toByteClassArray(parPayload));
	}
	
	/**
	 * Gestisce i byte ricevuti dal mezzo fisico e li tratta come risultato di un sampling ad eventi 
	 * @param payload byte ricevuti da interpretare
	 */
	private void manageData(Byte[] payload) {
		// Converto da array di byte (blob) a struttura
		MpptData data = new MpptData();
		
		do {
			try {
				payload = DataConverter.fromByteArray(data, payload);
				Record record = DBRecordUtil.prepareMPPTDataRecords(this, data, mpptDataRecordStructure, pDevNumber);
				enqueueRecord(record);
			}catch(IllegalArgumentException e) {
				SimpleLogger.log(this.getClass(), "Errore durante la conversione da array di byte a struttura: " + e.getMessage());
				break;
			}catch (PerlaDataTransformException e) {
				e.printStackTrace();
			}catch (OperationNotSupportedException e) {
				e.printStackTrace();
			}
		} while(payload.length > structSize);
	}
		
	/**
	 * Esegue l'enqueue di un record su tutte le pipe in uscita dell'FPC
	 * @param record record da inserire nelle pipes
	 */
	private synchronized void enqueueRecord(Record record) {
		for (Pipe<Record> p : pDataCollectorPipeOut) {
			p.enqueue(record);
		}
	}
	
/*	public synchronized Pipe<Record> registerQuery(ArrayList<String> parAttrbiutesNames) {
		Pipe<Record> tmpPipe = new Pipe<Record>("DSPicFPC " + pDevNumber + "_" + 
				pDataCollectorPipeOut.size() + " DataCollectorPipeOut");
		tmpPipe.start();
		pDataCollectorPipeOut.add(tmpPipe);
		return tmpPipe;
	}
*/
	@Override
	public boolean setSamplingTime(long parTime, OnUnsupportedSampleRateOptions parBehaviour) {
		return true;
	}
	
	/**
	 * Invia un null su tutte le pipe in uscita in modo che gli
	 * oggetti in attesa di dati dalle Pipe possano terminare
	 */
	private synchronized void stopRegisteredListeners() {
		for(Pipe<?> p : pDataCollectorPipeOut) {
			p.enqueue(null);
		}
	}
	
	/**
	 * Inner class per la gestione della lettura sulla pipe di ingresso
	 * @author Marco Fortunato
	 */
	private class PipeReader implements Runnable {
		
		public void run() {
			Waitable<Pipe<AdapterFpcMessage>> waitable;
			Pipe<AdapterFpcMessage> pipe;
			AdapterFpcMessage message;
			
			while(true) {
				waitable = pFpcPipeInWaiter.waitNext();
				pipe = waitable.getParentComponent();
				message = pipe.dequeue();
				
				if (message != null) {
				 	mngNormalMsg(message.getPayload());
				} else {
					pFpcPipeInWaiter.removeWaitable(waitable);
					pipe.stop();
					stopRegisteredListeners();
					break;
				}
			}
			SimpleLogger.log(this.getClass(), "MPPT_" + pDevNumber + " terminated!");
		}
	}

	@Override
	protected void initCoefficients() throws FPCInitializationPhaseException, PerlaCalibrationLoaderException, SQLException {
		this.coefficientMap = new HashMap<String, Double[]>();
		Field fields[] = MpptData.class.getDeclaredFields();
		for(Field field : fields){
			String fieldName = field.getName();
			Double []arrayList = PerlaCalibrationLoader.getCoefficient(this.pDeviceName, fieldName);
			this.coefficientMap.put(fieldName, arrayList);
		}
	}

	@Override
	public Pipe<Record> registerQuery(ArrayList<String> parAttrbiutesNames) {
		// TODO Auto-generated method stub
		
		Pipe<Record> tmpPipe = new Pipe<Record>("MPPT" + pDevNumber + "_" + 
				pDataCollectorPipeOut.size() + " DataCollectorPipeOut");
		tmpPipe.start();
		pDataCollectorPipeOut.add(tmpPipe);
		return tmpPipe;
	}

	@Override
	public void setUpdateAttributes(
			ArrayList<PairValue<String, ? extends Constant>> attributeToSetList) {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public Double[] getCoefficients(String s) throws OperationNotSupportedException {
		return this.coefficientMap.get(s);
	}
	
}
